Most modern computing systems, such as desktops and servers, include tools to allow a user to debug a user application on the computing system. Some debug tools enable a user to dynamically break into any routine in a user application and collect debugging and performance information non-disruptively.
A common debug tool can allows a user to insert probes, such as uprobes, on any instruction in an application's virtual address space. The user provides a file, such as an executable or a library, and an offset within the file to define an address. The debug tool can iterate through the processes currently running on the computing system to determine if the computing system is using the file. When the file is found within a running process, the instruction in the file at the defined address can be replaced with a breakpoint instruction (e.g., TRAP instruction), and the original bytes (the instruction at the defined address) can be saved. The running processes can be checked, and the corresponding files for the running processes including the provided file can be updated. When a central processing unit (CPU) in the computing system hits the provided address, a trap can occurs, the debug tool can be triggered, and a registered callback can be run.
However, this type of debug tool typically consumes memory, since the original bytes for all processes using the file are copied. If many processes use the file, a significant amount of memory can be used. Moreover, if a process never hits the breakpoint instruction, the original bytes for that process can be stored unnecessarily. In addition, storing the original bytes for all processes is time consuming. Furthermore, specialized code may be required for registering the trap, and turning off the debugging capabilities for an application may require restoring the memory pages to their original state by loading the stored original bytes for each memory page used by each process associated with the application.